findCode.js ➔ _findFencedCode   B
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 79

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 79
rs 8.8701

3 Functions

Rating   Name   Duplication   Size   Complexity  
B findCode.js ➔ ... ➔ _findClosingCodeFence 0 27 6
B findCode.js ➔ ... ➔ _findOpeningCodeFence 0 29 2
A findCode.js ➔ ... ➔ _findEndOfBlock 0 11 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
/** findMdpInsert and findCode functions use a similar layout to return the location and contents
2
  *   .start          => points at the character in the string why the other item starts (ie. comment or code block)
3
  *   .length         => is the overall length of the comment or code block.
4
  *   .internalStart  => points at the character in the string where the internal payload starts
5
  *   .internalLength => is the length of the internal payload
6
  *   .commandString  => is the command string found within the particular item
7
  *   .info           => is a structure containing further info about what was found
8
  * if start is returned as -1 then nothing was found
9
  *
10
  * The internalStart/internaLength defines the internal content which will be replaced. This does not include
11
  * leading and lagging CRLF/LF. So the replacement text is not required to have either leading or lagging line
12
  * endings. However, if the internalLength is negative this means that leading CRLF or LF must be added by the insertion
13
  * routine. The reason for this is that it allows insertions between code fences or mdpInsert pairs which have zero lines
14
  * between them.
15
  *
16
**/
17
18
const {earlierOf} = require('./helpers.js')
19
20
export function findCode (txt, start) {
21
  /**
22
  * finds the next code in the string provided starting at position start
23
  * returns an object containing start, length, internalStart, internalLength
24
  *
25
  * there are three types of code insertion - code span (inline), fenced code and indented code
26
  *
27
  * eg. span (1 or more backticks):
28
  * some text ``echo myfile.txt`` more text
29
  *
30
  * eg. indented (4 or more indent spaces):
31
  * some text
32
  *     function test() {
33
  *       console.log('test')
34
  *     }
35
  * more text
36
  *
37
  * eg. fenced (3 or more backticks on a row on their own)
38
  * some text
39
  * ``` js
40
  * function test() {
41
  *   console.log('test')
42
  * }
43
  * ```
44
  * more text
45
  *
46
  **/
47
  let x = _findFencedCode(txt, start)
48
  let y = _findIndentedCode(txt, start)
49
  let z = _findCodeSpan(txt, start)
50
51
  return earlierOf(x, earlierOf(y, z))
52
}
53
54
function _findFencedCode (txt, start) {
55
  // if the internalLength is returned as -1 this means that text cannot simply be inserted at the internalStart
56
  // location. Instead an additional preceding new line must be inserted along with the new text
57
  // another way to look at this is that the internal text is 1 character short
58
  // a value of -2 indicates a CRLF needs to be inserted
59
  let a = _findOpeningCodeFence(txt, start)
60
  if (a.start === -1) { return a }
61
  return _findClosingCodeFence(txt, a)
62
63
  function _findOpeningCodeFence (txt, start) {
64
    // returns the location and type of the next opening code fence
65
    let regex = /(^|\r\n|\n)([ ]{0,3}> |>|[ ]{0,0})(([ ]{0,3})([`]{3,}|[~]{3,})([^\n\r\0`]*))($|\r\n|\n)/g
66
    /** The regex groups are:
67
      * 0: the full match including any preamble block markup
68
      * 1: the leading new line character(s)
69
      * 2: the preamble consisting of block characters or nothing
70
      * 3: the full codeFence line without preamble
71
      * 4: any leading blank spaces at the start of the codeFence line
72
      * 5: the ` or ~ characters identifying the codeFence
73
      * 6: anything else on the line following the codeFence
74
      * 7: the final new line character(s)
75
    **/
76
    regex.lastIndex = start
77
    let regexResult = regex.exec(txt)
78
    if (regexResult === null) {
79
      return {start: -1}
80
    }
81
    let r = { start: regexResult.index + regexResult[1].length,
82
      info: {
83
        blockQuote: regexResult[2],
84
        spacesCount: regexResult[4].length,
85
        codeFence: regexResult[5]
86
      },
87
      commandString: regexResult[6].trim(),
88
      internalStart: regexResult.index + regexResult[0].length
89
    }
90
    return r
91
  }
92
93
  function _findClosingCodeFence (txt, opening) {
94
    // updates the passed result structure with the location and type of the next closing code fence
95
    // to match the opening cofeFence passed in
96
    let regex
97
    let r = JSON.parse(JSON.stringify(opening)) // create copy of opening structure passed in
98
    regex = RegExp('(^|\r\n|\n)([ ]{0,3}> |>|[ ]{0,0})[ ]{0,3}[' + r.info.codeFence[0] + ']{' + r.info.codeFence.length + ',}[ ]*($|\r\n|\n)', 'g')
99
    regex.lastIndex = r.internalStart - 2
100
    let regexResult = regex.exec(txt)
101
    if (opening.info.blockQuote.length !== 0) {
102
      // we are in a block quote so the codeFence will end at the earlier of the found regex OR end of the block quote
103
      let b = _findEndOfBlock(txt, r.internalStart)
104
      if (b !== -1 && (regexResult === null || b < (regexResult.index + regexResult[1].length))) {
105
        // the block end dictates the code block end
106
        r.internalLength = b - r.internalStart
107
        r.length = b - r.start
108
        return r
109
      }
110
    }
111
    if (regexResult === null) {
112
      r.internalLength = txt.length - r.internalStart
113
      r.length = txt.length - r.start
114
    } else {
115
      r.internalLength = regexResult.index - r.internalStart
116
      r.length = regexResult.index + regexResult[0].length - regexResult[3].length - r.start
117
    }
118
    return r
119
  }
120
121
  function _findEndOfBlock (txt, start) {
122
    // finds the first line which is not marked as block
123
    let regex = /(\r\n|\n)(?!([ ]{0,3}> |>))[^>\r\n]*/g
124
    regex.lastIndex = start
125
    let regexResult = regex.exec(txt)
126
    if (regexResult === null) {
127
      return -1
128
    } else {
129
      return regexResult.index
130
    }
131
  }
132
}
133
134
function _findIndentedCode (txt, start) {
135
  let regex = /((?:^|\r\n|\n)[ ]{4,}[^\r\n\0]*){1,}/g
136
  regex.lastIndex = start
137
  let regexResult = regex.exec(txt)
138
  if (regexResult === null) {
139
    return {start: -1}
140
  } else {
141
    return {
142
      start: regexResult.index,
143
      length: regexResult[0].length,
144
      internalStart: regexResult.index,
145
      internalLength: regexResult[0].length,
146
      info: {indent: regexResult[2]},
147
      commandString: ''
148
    }
149
  }
150
}
151
152
function _findCodeSpan (txt, start) {
153
  // finds an inline Code Span in the format: 'some text ``echo myfile.txt`` more text'
154
  // look for start
155
  let lookFrom = start
156
  while (true) {
157
    let s = _findCodeSpanStart(txt, lookFrom)
158
    if (s.start === -1) { return s }
159
    // look for end
160
    let e = _findCodeSpanEnd(txt, s)
161
    if (e.start !== -1) { return e }
162
    lookFrom = s.internalStart
163
  }
164
165
  function _findCodeSpanStart (txt, start) {
166
    let regex = /(^|[^`])(`+)[^`]/g
167
    // 1st capture group is the first (or no) character prior to the identifying `'s
168
    // 2nd group is the ` characters (however many there are)
169
    regex.lastIndex = start
170
    let regexResult = regex.exec(txt)
171
    if (regexResult === null) { return {start: -1} }
172
    let r = {
173
      start: regexResult.index + regexResult[1].length,
174
      internalStart: regexResult.index + regexResult[1].length + regexResult[2].length,
175
      info: {
176
        codeFence: regexResult[2]
177
      }
178
    }
179
    return r
180
  }
181
182
  function _findCodeSpanEnd (txt, opening) {
183
    let r = JSON.parse(JSON.stringify(opening)) // create copy of opening structure passed in
184
    let regex = RegExp('([^`])(' + r.info.codeFence + ')($|[^`])', 'g')
185
    regex.lastIndex = r.internalStart
186
    let regexResult = regex.exec(txt)
187
    if (regexResult === null) { return {start: -1} }
188
    r.internalLength = regexResult.index + regexResult[1].length - r.internalStart
189
    r.length = regexResult.index + regexResult[0].length - regexResult[3].length - r.start
190
    r.commandString = ''
191
    return r
192
  }
193
}
194